# -*coding: utf-8 -*-
# @Time     : 2025/7/30 21:15 
# @Author   : CHUYAO
# @Email    : 1204602307@qq.com
# @File     : django_lock.py
# @Software : PyCharm
# @Project ：cy_query
import traceback,hashlib
from functools import wraps
from redis.exceptions import LockError
from cy_query.conf import settings
# from cy_rest_framework.response import ErrorResponse
# from cy_rest_framework.models import ApiErrorLogModel
from django.http import Http404
from django.http.response import JsonResponse
from cy_query.utils.module_loading import import_string

caches = import_string(settings.DJANGO_CACHE_LOCK)
if settings.LOCK_API_ERROR_LOG:
    create_error_log = import_string(settings.LOCK_API_ERROR_LOG)
else:
    def create_error_log(request,exc,e):
        pass

if settings.ERROR_RESPONSE:
    ErrorResponse = import_string(settings.LOCK_API_ERROR_LOG)
else:
    class ErrorResponse(JsonResponse):
        def __init__(self,data=None,msg='error',code=400,*args,**kwargs):
            data = {
                'msg':msg,
                'data':data,
                'code':code
            }
            super().__init__(data,*args,**kwargs)

__all__ = ['ApiLock']


def get_lock_key(request,key,params,datas):
    param_hash = hashlib.md5()
    for param in params or []:
        value = request.query_params.get(param, "")
        param_hash.update(f"{param}={value}".encode())
    for data in datas or []:
        value = str(request.data.get(data, ""))
        param_hash.update(f"{data}={value}".encode())
    return key + f":{param_hash.hexdigest()}"


class ApiLock:
    try:
        cache = caches[settings.DJANGO_API_LOCK]
    except KeyError:
        cache = None

    @staticmethod
    def interface_lock_operate(key: str, params: list[str] = None, datas: list[str] = None, timeout=120,blocking_timeout=0):
        """
        参数级，不会对接口地址进行锁定,只会对请求参数（可选）key进行判断锁定
        接口函数锁，当用户多个接口同时触发,所有人都不允许操作
        :param key: 唯一key
        :param params: ?xx=xxx 请求的参数
        :param datas: 请求体的参数
        :param timeout: 锁自动释放时间，防止进程崩溃导致死锁
        :param blocking_timeout: 最大等待时间,默认设置为0，及无法获取到锁，立刻抛出异常
        :return:
        """

        def actual_decorator(function):
            @wraps(function)
            def wrapper_function(self, request, *args, **kwargs):
                if ApiLock.cache is None:raise ImportError
                lock_key = get_lock_key(request=request,key=f"interface_lock_operate:{key}",params=params,datas=datas)
                try:
                    with ApiLock.cache.lock(lock_key,timeout=timeout,blocking_timeout=blocking_timeout):
                        try:
                            result = function(self, request, *args, **kwargs)
                        except Http404:
                            result = ErrorResponse(msg=f'接口地址不正确', code=404)
                        except Exception as e:
                            exc = traceback.format_exc()
                            create_error_log(request=request,exc=exc,e=e)
                            result = ErrorResponse(msg=str(e))
                except LockError:
                    result = ErrorResponse(msg='请求过于频繁，请稍后再试')
                except Exception as e:
                    exc = traceback.format_exc()
                    create_error_log(request=request,exc=exc,e=e)
                    result = ErrorResponse(msg='服务器内部错误')
                return result

            return wrapper_function

        return actual_decorator

    @staticmethod
    def anti_shake(timeout: int = 10, check_data: bool = True):
        """
        防抖api接口,一般post或put的时候使用
        check_data:防抖的时候是否校验数据是否一致导致的重复提交
        """

        def actual_decorator(function):
            @wraps(function)
            def wrapper_function(self, request, *args, **kwargs):
                if ApiLock.cache is None: raise ImportError
                key = 'ANTI_SHAKE-%s:%s-%s-%s' % (
                    request.method, request.path, str(request.user.id), str(request.request_data) if check_data else '')
                if ApiLock.cache.set(key, 1, nx=True, timeout=10 if callable(timeout) else timeout):
                    try:
                        return function(self, request, *args, **kwargs)
                    except Http404:
                        return ErrorResponse(msg=f'接口地址不正确', code=404)
                    except Exception as e:
                        exc = traceback.format_exc()
                        create_error_log(request=request,exc=exc,e=e)
                        return ErrorResponse(msg=str(e))
                return ErrorResponse(msg='短时间内,请勿重复提交')

            return wrapper_function

        if callable(timeout):
            return actual_decorator(timeout)
        return actual_decorator

    @staticmethod
    def prevent_concurrency(params: list[str] = None, datas: list[str] = None, timeout=120,blocking_timeout=30,lock_pk=True):
        """
        参数级，!会对接口地址进行锁定,只会对请求参数（可选），其他请求会被等待
        :param params: ?xx=xxx 请求的参数
        :param datas: 请求体的参数
        :param timeout: 锁自动释放时间，防止进程崩溃导致死锁
        :param blocking_timeout: 最大等待时间
        :param lock_pk: 如有pk参数，是否需要对pk进行加锁，默认加锁,如果没有pk时会忽略该参数
        :return:
        """

        def actual_decorator(function):
            @wraps(function)
            def wrapper_function(self, request, *args, **kwargs):
                if ApiLock.cache is None: raise ImportError
                lock_key:str = request.path
                if kwargs.get('pk') and not lock_pk:
                    lock_key = lock_key.replace(str(kwargs['pk']),'lock_pk')
                lock_key = get_lock_key(request=request,key=lock_key,params=[] if callable(params) else params,datas=datas)
                try:
                    with ApiLock.cache.lock(lock_key, timeout=timeout, blocking_timeout=blocking_timeout):
                        try:
                            result = function(self, request, *args, **kwargs)
                        except Http404:
                            result = ErrorResponse(msg=f'接口地址不正确', code=404)
                        except Exception as e:
                            exc = traceback.format_exc()
                            create_error_log(request=request,exc=exc,e=e)
                            result = ErrorResponse(msg=str(e))
                except LockError:
                    result = ErrorResponse(msg='请求过于频繁，请稍后再试')
                except Exception as e:
                    exc = traceback.format_exc()
                    create_error_log(request=request,exc=exc,e=e)
                    result = ErrorResponse(msg='服务器内部错误')
                return result

            return wrapper_function

        if callable(params):
            return actual_decorator(params)
        return actual_decorator
